package com.keepc.excel.service;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.keepc.excel.domain.DataRecord;
import com.keepc.excel.mapper.DataRecordMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.*;

/**
 * 数据导出服务类
 *
 * @author keepc
 */
@Service
@Slf4j
public class DataExportService {

	// 分页大小
	private static final int PAGE_SIZE = 20 * 10000;
	// 每个 Sheet 的最大数据量
	private static final int SHEET_SIZE = 1000000;
	// 每个Sheet的写入批次大小
	private static final int BATCH_SIZE = 2 * 10000;
	// 生产者线程数量
	private static final int PRODUCT_THREAD_COUNT = 4;

	@Autowired
	private DataRecordMapper dataRecordMapper;

	/**
	 * 导出数据到 Excel 文件
	 *
	 * @param exportPath 导出文件的路径
	 * @param excelName  导出文件的名称
	 */
	public void exportData(String exportPath, String excelName) {
		// 初始化页码和 Sheet 相关变量
		int page = 0;
		int sheetDataSize = 0;
		int sheetNo = 1;

		try (FileOutputStream outputStream = new FileOutputStream(exportPath + "\\" + excelName + ".xlsx")) {
			// 创建 ExcelWriter 对象用于写入数据
			ExcelWriter excelWriter = EasyExcel.write(outputStream).build();
			// 初始化 WriteSheet 对象
			WriteSheet writeSheet = createWriteSheet(sheetNo);

			// 循环读取数据并写入 Excel
			while (true) {
				page++;
				// 分页查询数据
				List<DataRecord> openList = dataRecordMapper.selectDataByPage((page - 1) * PAGE_SIZE, PAGE_SIZE);

				// 如果查询不到数据，说明数据导出完成
				if (openList == null || openList.isEmpty()) {
					log.info("数据导出完成，总页数: {}, 总 Sheet 数: {}", page - 1, sheetNo);
					break;
				}

				// 更新当前 Sheet 的数据量
				sheetDataSize += openList.size();

				// 如果当前 Sheet 数据量超过设定的最大值，创建新的 Sheet
				if (sheetDataSize > SHEET_SIZE) {
					sheetNo++;
					// 重置当前 Sheet 数据量
					sheetDataSize = openList.size();
					// 创建新的 WriteSheet
					writeSheet = createWriteSheet(sheetNo);
				}

				// 写入数据
				excelWriter.write(openList, writeSheet);
				log.info("第 {} 页数据写入成功，当前 Sheet: {}", page, sheetNo);
			}

			// 完成写入
			excelWriter.finish();
			log.info("Excel 文件生成完成，路径: {}", exportPath + excelName + ".xlsx");

		} catch (Exception e) {
			// 记录导出数据时的错误信息
			log.error("导出数据失败，路径: {}, 文件名: {}, 错误信息: {}", exportPath, excelName, e.getMessage(), e);
		}
	}


	/**
	 * 多线程分页查询、动态Sheet分配和高效批量写入；通过缓存队列减少IO频率，利用生产者-消费者模式乱序导出数据到Excel
	 *
	 * @param exportPath 导出文件的路径
	 * @param excelName  导出文件的名称
	 */
	public void exportData2(String exportPath, String excelName) {
		// 获取数据总量
		Long count = dataRecordMapper.selectCount(null);
		// 计算需要多少个Sheet
		int sheetCount = (int) Math.ceil((double) count / SHEET_SIZE);
		// 线程安全队列：Key为Sheet索引，Value为对应Sheet的数据批次
		Map<Integer, Queue<List<DataRecord>>> sheetQueues = new ConcurrentHashMap<>();
		for (int i = 0; i < sheetCount; i++) {
			sheetQueues.put(i, new LinkedBlockingQueue<>());
		}

		// 1. 初始化线程池（生产者：数据查询，消费者：数据写入）
		ExecutorService queryExecutor = Executors.newFixedThreadPool(PRODUCT_THREAD_COUNT);
		ExecutorService writeExecutor = Executors.newSingleThreadExecutor();
		// 2. 创建ExcelWriter（文件路径、数据模型）
		String fileName = exportPath + "\\" + excelName + ".xlsx";
		ExcelWriter excelWriter = EasyExcel.write(fileName, DataRecord.class).build();
		// 3. 启动消费者线程：异步批量写入
		Future<?> writeFuture = writeExecutor.submit(() -> {
			try {
				while (true) {
					boolean hasData = false;
					for (int sheetIndex = 0; sheetIndex < sheetCount; sheetIndex++) {
						Queue<List<DataRecord>> queue = sheetQueues.get(sheetIndex);
						List<DataRecord> batchData = new ArrayList<>(BATCH_SIZE);

						// 批量获取数据
						synchronized (queue) {
							while (!queue.isEmpty() && batchData.size() < BATCH_SIZE) {
								List<DataRecord> dataList = queue.poll();
								if (dataList != null) {
									batchData.addAll(dataList);
								}
							}
						}

						if (!batchData.isEmpty()) {
							// 动态获取或创建WriteSheet
							WriteSheet writeSheet = createWriteSheet(sheetIndex);
							excelWriter.write(batchData, writeSheet);
							hasData = true;
						}
					}
					// 所有队列为空且生产者已结束，则退出
					if (!hasData && queryExecutor.isTerminated()) {
						break;
					}
					Thread.sleep(50); // 避免CPU空转
				}
			} catch (Exception e) {
				log.error("数据写入失败", e);
			}
		});
		// 4. 生产者线程：分页查询并提交到队列
		int totalPages = (int) (count / PAGE_SIZE); // 总页数
		int pagesPerThread = totalPages / PRODUCT_THREAD_COUNT; // 每个线程处理多少页
		List<Future<?>> futures = new ArrayList<>();
		for (int i = 0; i < PRODUCT_THREAD_COUNT; i++) {
			final int threadId = i;
			futures.add(queryExecutor.submit(() -> {
				int startPage = threadId * pagesPerThread + 1;
				int endPage = (threadId == PRODUCT_THREAD_COUNT - 1) ? totalPages : startPage + pagesPerThread - 1;
				for (int page = startPage; page <= endPage; page++) {
					List<DataRecord> pageData = dataRecordMapper.selectDataByPage((page - 1) * PAGE_SIZE, PAGE_SIZE);
					// log.info("线程 {} 查询第 {} 页数据完成，数据量: {}", threadId, page, pageData.size());
					// 分配Sheet
					int sheetIndex = (page * PAGE_SIZE - 1) / SHEET_SIZE;
//					log.info("线程 {} 把第 {} 页数据分配到第 {} 个Sheet", threadId, page, sheetIndex);
					Queue<List<DataRecord>> queue = sheetQueues.get(sheetIndex);
					synchronized (queue) {
						queue.add(pageData);
					}
				}
			}));
		}

		// 5. 等待生产者完成
		for (Future<?> future : futures) {
			try {
				future.get();
			} catch (InterruptedException | ExecutionException e) {
				log.error("数据查询失败", e);
				throw new RuntimeException(e);
			}
		}
		queryExecutor.shutdown();

		// 6. 等待消费者完成并释放资源
		try {
			writeFuture.get();
		} catch (InterruptedException | ExecutionException e) {
			log.error("数据写入失败", e);
			throw new RuntimeException(e);
		}
		excelWriter.finish();
		writeExecutor.shutdown();
	}

	/**
	 * 创建 WriteSheet 对象的私有方法
	 * 该方法用于初始化和配置 WriteSheet，以便进行数据写入操作
	 *
	 * @param sheetNo 工作表编号，用于区分不同的工作表
	 * @return 返回配置好的 WriteSheet 对象
	 */
	private WriteSheet createWriteSheet(int sheetNo) {
		// 使用 EasyExcel 的 writerSheet 方法创建 WriteSheet 对象
		// 参数 sheetNo 用于指定工作表的编号，"sheet-" + sheetNo 用于指定工作表的名称
		// 使用 head 方法指定数据模型类为 DataRecord，以自动映射数据到工作表
		// 注册 LongestMatchColumnWidthStyleStrategy 处理器，以自动调整列宽
		return EasyExcel.writerSheet(sheetNo, "sheet-" + sheetNo).head(DataRecord.class).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).build();
	}
}